/*
 * Copyright 2010-2011 CGT&DC ISA RAS
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package websolve.workflow.wfms;

import java.net.URI;
import java.text.SimpleDateFormat;
import java.util.Iterator;
import java.util.Locale;
import java.util.logging.Logger;

import org.codehaus.jettison.json.JSONObject;

import websolve.workflow.runtime.Task;
import websolve.workflow.runtime.TaskState;
import websolve.workflow.util.ErrorUtils;

import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;

public class ServiceTask extends Task {

    private static ClientConfig clientConf = null;

    private URI serviceUrl;
    private String user;

    private SimpleDateFormat df = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z", Locale.ENGLISH);

    public static void setClientConfig(ClientConfig config) {
        if (clientConf == null) {
            clientConf = config;
        }
    }

    public ServiceTask(String name, URI serviceUrl, String user) throws Exception {
        super(name);
        this.serviceUrl = serviceUrl;
        this.user = user;
        log.fine("Initializing service task (name: " + name + ", URI: " + serviceUrl + ")");

        // get service description
        Client client = Client.create();
        WebResource service = client.resource(serviceUrl);
        long timer = System.currentTimeMillis();
        JSONObject desc = service.accept("application/json").get(JSONObject.class);
        log.finer("GET " + serviceUrl + ": successful, took " + (System.currentTimeMillis() - timer) + " ms");
        log.finer("GET " + serviceUrl + " response: " + desc);

        // initialize input/output params
        JSONObject inputs = desc.getJSONObject("inputs");
        Iterator<String> keysI = inputs.keys();
        while (keysI.hasNext()) {
            String paramName = keysI.next();
            JSONObject paramDesc = inputs.getJSONObject(paramName);
            String type = paramDesc.getString("type");
            addInput(paramName, type);
            log.fine("Added input " + paramName + " (type: " + type + ")");
        }
        JSONObject outputs = desc.getJSONObject("outputs");
        keysI = outputs.keys();
        while (keysI.hasNext()) {
            String paramName = keysI.next();
            JSONObject paramDesc = outputs.getJSONObject(paramName);
            String type = paramDesc.getString("type");
            addOutput(paramName, type);
            log.fine("Added output " + paramName + " (type: " + type + ")");
        }
    }

    @Override
    public void run() {
        log.fine("Running service task (name: " + getName() + ", URI: " + serviceUrl + ")");
        setState(TaskState.RUNNING);
        try {
            // create JSON request
            JSONObject request = new JSONObject();
            for (String param : getInputs().keySet()) {
                Object value = getInputValue(param);
                if (value != null) {
                    if (getInput(param).getType() != null && getInput(param).getType().equals("file")) {
                        value = new JSONObject().put("url", value);
                    }
                    request.put(param, value);
                }
            }
            log.finer("POST " + serviceUrl + " request: " + request);

            // submit request to service
            Client client = (clientConf==null)?Client.create():Client.create(clientConf);
            WebResource service = client.resource(serviceUrl);
            long timer = System.currentTimeMillis();
            ClientResponse response = service.accept("application/json").type("application/json").
                    header("WebSolve.Proxy.User", user).post(ClientResponse.class, request);
            log.fine("Invoked service " + serviceUrl);
            log.finer("POST " + serviceUrl + ": successful, took " + (System.currentTimeMillis() - timer) + " ms");
            log.finer("POST " + serviceUrl + " response: " + response);

            int status = response.getStatus();
            log.finer("POST " + serviceUrl + " response status: " + status);
            if (status == 202) {
                URI jobUri = response.getLocation();
                log.finer("Job URI: " + jobUri);
                WebResource job = client.resource(jobUri);
                JSONObject jobStatus = response.getEntity(JSONObject.class);
                log.finer("Job " + jobUri + " status: " + jobStatus);
                String jobState = jobStatus.getString("state");
                log.fine("Job " + jobUri + " state: " + jobState);
                setState(TaskState.RUNNING);
                setInfo("Service request state: " + jobState + ". Info: " + jobStatus.optString("info", ""));
                if (jobStatus.has("error")) {
                    setError(jobStatus.getString("error"));
                    log.severe("Job " + jobUri + " error: " + jobStatus.getString("error"));
                }
                while (!jobState.equals("DONE") && !jobState.equals("FAILED")) {
                    Thread.sleep(1000);
                    if (!getState().equals(TaskState.CANCELLED)) {
                        timer = System.currentTimeMillis();
                        jobStatus = job.accept("application/json").get(JSONObject.class);
                        log.finer("GET " + jobUri + ": successful, took " + (System.currentTimeMillis() - timer)
                                + " ms");
                        log.finer("GET " + jobUri + " response: " + jobStatus);
                        jobState = jobStatus.getString("state");
                        log.fine("Job " + jobUri + " state: " + jobState);
                        setInfo("Service request state: " + jobState + ". Info: " + jobStatus.optString("info", ""));
                        if (jobStatus.has("error")) {
                            setError(jobStatus.getString("error"));
                            log.severe("Job " + jobUri + " error: " + jobStatus.getString("error"));
                        }
                    } else {
                        job.delete();
                        log.fine("Sent DELETE to job " + jobUri);
                        break;
                    }
                }
                if (!getState().equals(TaskState.CANCELLED)) {
                    if (jobState.equals("DONE")) {
                        JSONObject resultJSON = jobStatus.getJSONObject("result");
                        log.finer("Job " + jobUri + " result: " + resultJSON);
                        for (String param : getOutputs().keySet()) {
                            if (resultJSON.has(param)) {
                                Object paramValue;
                                if (getOutput(param).getType().equals("file")) {
                                    paramValue = new URI(resultJSON.getJSONObject(param).getString("url"));
                                } else {
                                    paramValue = resultJSON.get(param);
                                }
                                setOutputValue(param, paramValue);
                                log.fine("Set output param: " + param + " = " + paramValue);
                            }
                        }
                        setState(TaskState.DONE);
                    } else {
                        setState(TaskState.FAILED);
                    }
                }
            } else {
                setError(response.getEntity(String.class));
                setState(TaskState.FAILED);
                log.severe("POST " + serviceUrl + " failed: " + response.getEntity(String.class));
            }

        } catch (Exception e) {
            setError(ErrorUtils.stackTraceToString(e));
            setState(TaskState.FAILED);
            log.severe("Error running service task (name: " + getName() + ", URI: " + serviceUrl + "): "
                    + ErrorUtils.stackTraceToString(e));
        }
    }

    private static final Logger log = Logger.getLogger(ServiceTask.class.getName());
}
